package com.sikaryofficial.backend.message.consumer;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.sikaryofficial.backend.annotation.TenantFilter;
import com.sikaryofficial.backend.constant.ActivityConstant;
import com.sikaryofficial.backend.constant.ActivityReserveStatusEnum;
import com.sikaryofficial.backend.constant.ActivityTypeEnum;
import com.sikaryofficial.backend.constant.MyStatisticsTypeEnum;
import com.sikaryofficial.backend.domain.entity.Activity;
import com.sikaryofficial.backend.domain.entity.ActivityPrizeRelation;
import com.sikaryofficial.backend.domain.entity.ActivityReserve;
import com.sikaryofficial.backend.domain.entity.ActivityResult;
import com.sikaryofficial.backend.message.body.ActivityAttendMessage;
import com.sikaryofficial.backend.message.body.ActivityResultUserInfoMessage;
import com.sikaryofficial.backend.message.product.ProductProcess;
import com.sikaryofficial.backend.service.IActivityPrizeRelationService;
import com.sikaryofficial.backend.service.IActivityReserveService;
import com.sikaryofficial.backend.service.IActivityResultService;
import com.sikaryofficial.backend.service.IActivityService;
import com.sikaryofficial.backend.service.activity.ActivityCacheService;
import com.sikaryofficial.backend.service.statistics.MyStatisticsProcessor;
import com.sikaryofficial.backend.service.statistics.MyStatisticsEvent;
import com.sikaryofficial.common.core.context.SecurityContextHolder;
import com.sikaryofficial.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import static com.sikaryofficial.backend.constant.RedisCacheKey.*;

@Component
@Slf4j
// @RefreshScope  此处不能加这个注解，否则消费消息异常
public class ActivityAttendProcess {
    @Autowired
    private IActivityReserveService activityReserveService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private IActivityService activityService;
    @Autowired
    private IActivityResultService activityResultService;
    @Autowired
    private MyStatisticsProcessor myListener;
    @Autowired
    private RedissonClient redissonClient;
    // 保存2周内中奖结果
    @Value("${hismk.backend.activity.attend.resultSaveDay}")
    private Long resultSaveDay;
    @Value("${hismk.backend.activity.reserve.statusSaveDay}")
    private Long statusSaveDay;
    @Value("${hismk.common.zoneId}")
    private String zoneId;
    @Autowired
    private ProductProcess productProcess;
    @Autowired
    private IActivityPrizeRelationService relationService;
    @Autowired
    private ActivityCacheService activityCacheService;

    /**
     * 消息监听
     * <p>
     * 抢购业务-消费流程
     *
     * @param message 消息
     */
    @StreamListener(MQConsumer.BINDINGS_ACTIVITY_PURCHASE_INPUT)
    @Transactional(rollbackFor = Exception.class)
    public void onActivityAttendMessage(@Payload ActivityAttendMessage message) {
        log.info("=========================################==================================");
        log.info("[onMessage][线程编号:{} 消息内容：{}]", Thread.currentThread().getId(), message);
        log.info("==========================###################===============================");
        // 设置品牌
        SecurityContextHolder.setBrand(message.getBrand());
        /**
         * 抢购业务 ， 我们只判定库存是否充足 ，不需要判断是否已经参与过活动
         *
         * 不要涉及数据库操作，这是一个高频操作，我们应该把它放到缓存中，减少数据库压力
         *
         * 目前计划采用MQ异步消息处理
         *
         */
        // 1、根据activityId 查询当前用户的预约信息
        ActivityReserve activityReserve = activityReserveService.getOne(new LambdaQueryWrapper<ActivityReserve>()
                .eq(ActivityReserve::getDeletedVersion, 0L)
                .eq(ActivityReserve::getReserveStatus, ActivityReserveStatusEnum.RESERVED.getCode())
                .eq(ActivityReserve::getActivityId, message.getActivityId())
                .eq(ActivityReserve::getCreatedBy, message.getUserId())
        );
        if (Objects.isNull(activityReserve)) {
            // 没有预约，不能抢购
            log.error("You have not already applied for this activity.");
            // 清除品牌
            SecurityContextHolder.remove();
            return;
        }
        // 活动抢购
        doActivityAttend(message, activityReserve);
        // 清除品牌
        SecurityContextHolder.remove();
    }

    /**
     * 活动抢购
     *
     * @param message
     * @param activityReserve
     */
    @TenantFilter
    private void doActivityAttend(ActivityAttendMessage message, ActivityReserve activityReserve) {
        // 查询已上架，并且在活动时间范围的活动
        Activity activity = activityService.getOne(new LambdaQueryWrapper<Activity>()
                .eq(Activity::getActivityId, message.getActivityId())
                .eq(Activity::getDeletedVersion, 0L)
                .eq(Activity::getHasEffect, ActivityConstant.EFFECT_YES)
        );
        if (!activityCheck(activity)) {
            return;
        }

        ActivityPrizeRelation relation = relationService.getOne(new LambdaQueryWrapper<ActivityPrizeRelation>()
                .eq(ActivityPrizeRelation::getActivityId, message.getActivityId())
                .eq(ActivityPrizeRelation::getActivityPrizeId, message.getActivityPrizeId())
                .eq(ActivityPrizeRelation::getDeletedVersion, 0L)
        );
        if (Objects.isNull(relation)) {
            log.error("活动没有奖项关系数据");
            return;
        }
        // 抢购前判定下库存
        Integer realStock = activityCacheService.getRealStock(message.getActivityId(), message.getActivityPrizeId());
        Integer stockExt = activityCacheService.getStockExt(message.getActivityId(), message.getActivityPrizeId());
        log.info("Consumer doActivityAttend : StartTime0 {},EndTime0 {}", activity.getStartTime(), activity.getEndTime());
        // 假设 LocalDateTimeUtil.of() 返回一个 LocalDateTime 对象
        LocalDateTime localDateTime = LocalDateTimeUtil.of(new Date());
        // 操作系统 设置UTC ;  数据设置为 GMT+0 ; 此处设置为 UTC
        ZonedDateTime utcDateTime = localDateTime.atZone(ZoneId.of(zoneId));
        // 将 UTC 时区的 ZonedDateTime 转换为 Date
        Date utcDate = Date.from(utcDateTime.toInstant());
        log.info("Consumer doActivityAttend : now1 {}", utcDate);
        boolean isActivityTime = utcDate.compareTo(activity.getStartTime()) >= 0
                && activity.getEndTime().compareTo(utcDate) >= 0;
        if (!isActivityTime) {
            // 当前活动未上架或不在活动时间
            log.error("The current activity is not within the activity time.");
            return;
        }
        String lockKey = MessageFormat.format(ACTIVITY_LOCK_CACHE, message.getActivityId());
        // 判断库存是否充足
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(1000, 1000, TimeUnit.MILLISECONDS)) {
                if (realStock > 0) {
                    // 扣减库存
                    attendProcess(message, activityReserve, relation);
                    log.info("realStock  be reduce.");
                    redisService.setCacheObject(activityCacheService.getCacheStockKey(message.getActivityId(), message.getActivityPrizeId()),
                            realStock - 1);
                    log.info("realStock cache  be reduce.");
                    return;
                }
                // 判断扩展库存是否充足
                if (realStock == 0 && stockExt > 0) {
                    // 如果库存为0，则进入随机派送阶段
                    // 扣减活动库存
                    log.info("stockExt will be reduce.");
                    int randomStock = ThreadLocalRandom.current().nextInt(1, 101);
                    // 添加随机派送算法：能否被2整除，如果是，则随机派送成功
                    if (randomStock % 3 == 0) {
                        // 随机派送成功
                        attendProcess(message, activityReserve, relation);
                        log.info("stockExt be reduce.");
                        redisService.setCacheObject(activityCacheService.getCacheStockExtKey(message.getActivityId(), message.getActivityPrizeId()),
                                stockExt - 1);
                        log.info("stockExt cache be reduce.");
                        return;
                    }
                }
                // 其他情况，更新预约状态
                updateReserveStatus(message);
            } else {
                // 其他情况，更新预约状态
                updateReserveStatus(message);
            }
        } catch (InterruptedException e) {
            log.error("抢购失败，锁被占用，请稍后再试");
            log.error("异常：{}", e);
            Thread.currentThread().interrupt();
        } finally {
            // 释放互斥锁
            lock.unlock();
        }
    }

    /**
     * 活动相关检查
     *
     * @param activity
     */
    private boolean activityCheck(Activity activity) {
        if (Objects.isNull(activity) || !Objects.equals(activity.getHasOnline(), ActivityConstant.ON_LINE)
                || !StringUtils.equalsAny(
                activity.getActivityType().toString(),
                ActivityTypeEnum.PURCHASE_ACTIVATE_NOW.getCode().toString(),
                ActivityTypeEnum.PURCHASE.getCode().toString())
        ) {
            // 当前活动未上架或不在活动时间
            log.error("活动不存在 | 非线上活动 | 活动类型不正确 ");
            return false;
        }
        return true;
    }

    /**
     * 抢购后续处理
     *
     * @param message         消息
     * @param activityReserve 预约对象
     * @param relation        活动奖项关联对象
     */
    private void attendProcess(ActivityAttendMessage message, ActivityReserve activityReserve, ActivityPrizeRelation relation) {
        // 1、更新预约状态
        updateReserveStatus(message);
        boolean flag = relationService.update(new ActivityPrizeRelation().setStock(relation.getStock() - 1)
                        .setUpdatedBy(message.getUserId())
                        .setUpdatedTime(new Date()),
                new LambdaQueryWrapper<ActivityPrizeRelation>()
                        .eq(ActivityPrizeRelation::getDeletedVersion, 0L)
                        .eq(ActivityPrizeRelation::getActivityPrizeId, relation.getActivityPrizeId())
                        .eq(ActivityPrizeRelation::getStock, relation.getStock())
                        .gt(ActivityPrizeRelation::getStock, 0)
        );
        if (!flag) {
            log.error("抢购失败，活动库存不足，请稍后再试");
            return;
        }
        // 3、生成抢购结果
        Long activityResultId = IdWorker.getId();
        activityResultService.save(new ActivityResult().setActivityResultId(activityResultId).setActivityId(message.getActivityId())
                .setActivityReserveId(activityReserve.getActivityReserveId())
                .setPrizeId(relation.getPrizeId())
                .setCreatedBy(message.getUserId())
                .setCreatedName(message.getUserName())
                .setCreatedTime(new Date())
        );
        // TODO: 4、异步 补充activityResult用户其他信息字段
        ActivityResultUserInfoMessage userInfoMessage = new ActivityResultUserInfoMessage();
        userInfoMessage.setActivityResultId(activityResultId);
        productProcess.sendActivityResultMessage(userInfoMessage);
        // 5、缓存抢购记录-缓存24小时
        redisService.setCacheObject(MessageFormat.format(ACTIVITY_ATTEND_RESULT_CACHE, message.getActivityId(), message.getUserId()), true
                , resultSaveDay, TimeUnit.DAYS);
        // 6、添加中奖数量统计
        myListener.addMyStatistics(new MyStatisticsEvent(MyStatisticsTypeEnum.RESERVATION_AWARDS_NUM.getBusinessType(), message.getUserId(), activityResultId));
    }

    /**
     * 更新预约状态
     *
     * @param message 消息
     */
    private void updateReserveStatus(ActivityAttendMessage message) {
        // 1、 更新 预约状态
        ActivityReserve activityReserve = activityReserveService.getOne(new LambdaQueryWrapper<ActivityReserve>()
                .eq(ActivityReserve::getDeletedVersion, 0L)
                .eq(ActivityReserve::getReserveStatus, ActivityReserveStatusEnum.RESERVED.getCode())
                .eq(ActivityReserve::getActivityId, message.getActivityId())
                .eq(ActivityReserve::getCreatedBy, message.getUserId())
        );
        if (Objects.isNull(activityReserve)) {
            log.error("You have not already applied for this activity.");
            log.error("预约状态有问题 getActivityReserveId={}", message);
        }
        boolean flag = activityReserveService.updateById(new ActivityReserve().setReserveStatus(ActivityReserveStatusEnum.EXPIRED.getCode())
                .setUpdatedBy(message.getUserId())
                .setUpdatedTime(new Date())
                .setActivityReserveId(activityReserve.getActivityReserveId())
        );
        if (flag) {
            // 重置预约缓存状态
            redisService.setCacheObject(MessageFormat.format(ACTIVITY_RESERVE_STATUS_CACHE, message.getActivityId(), message.getUserId()), ActivityReserveStatusEnum.EXPIRED.getCode()
                    , statusSaveDay, TimeUnit.DAYS);
            // 4、缓存抢购记录-缓存24小时
            redisService.setCacheObject(MessageFormat.format(ACTIVITY_ATTEND_RESULT_CACHE, message.getActivityId(), message.getUserId()), false
                    , resultSaveDay, TimeUnit.DAYS);
            // 添加预约过期数量统计
            myListener.addMyStatistics(new MyStatisticsEvent(MyStatisticsTypeEnum.RESERVATION_EXPIRE_NUM.getBusinessType(), message.getUserId(), activityReserve.getActivityReserveId()));
        }
    }
}
